package com.basker.pisces.domain.entity.meta;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.basker.pisces.core.Element;
import com.basker.pisces.core.ISupportSort;
import com.basker.pisces.core.meta.IDataObjectMeta;
import com.basker.pisces.core.meta.annotation.CollectionField;
import com.basker.pisces.core.meta.annotation.DataField;
import com.basker.pisces.core.query.meta.QueryMeta;
import com.basker.pisces.core.serialize.xml.DataContractXmlSerializer;
import com.basker.pisces.domain.ISupportEntityBuild;
import com.basker.pisces.domain.ISupportEntityLimit;
import com.basker.pisces.domain.command.meta.Command;
import com.basker.pisces.domain.entity.data.IDataEntity;
import com.basker.pisces.domain.entity.data.IRowListDataEntity;
import com.basker.pisces.domain.property.meta.CreateByProperty;
import com.basker.pisces.domain.property.meta.CreateTimeProperty;
import com.basker.pisces.domain.property.meta.IdProperty;
import com.basker.pisces.domain.property.meta.Property;
import com.basker.pisces.domain.property.meta.PropertyCollection;
import com.basker.pisces.domain.property.meta.StatusProperty;
import com.basker.pisces.domain.property.meta.TextProperty;
import com.basker.pisces.domain.property.meta.UpdateByProperty;
import com.basker.pisces.domain.property.meta.UpdateTimeProperty;
import com.basker.pisces.domain.validation.meta.Validation;
import com.basker.pisces.exception.ExceptionFactory;
import com.basker.pisces.resource.StringResources;

/**
 * 实体，描述一个业务对象的结构与业务逻辑
 *
 * @author hangwen
 */
public abstract class Entity extends AbstractEntity {

    @CollectionField
    private List<Command> commands = new ArrayList<>();
    @CollectionField
    private List<Validation> validations = new ArrayList<>();
    @DataField
    private String codePropertyName;
    @DataField
    private String namePropertyName;
    @DataField
    private Boolean enableDefaultDisplayNameRule = true;
    private transient TextProperty codeProperty;
    private transient TextProperty nameProperty;
    private transient StatusProperty[] statusProperties;
    private transient IdProperty idProperty;
    private transient CreateByProperty createByProperty;
    private transient CreateTimeProperty createTimeProperty;
    private transient UpdateByProperty updateByProperty;
    private transient UpdateTimeProperty updateTimeProperty;
    private transient Map<String, AbstractEntity> entityMap = new HashMap<>();
    private transient Map<String, Property<?>> propertyMap = new HashMap<>();
    private transient Map<String, Validation> validationMap = new HashMap<>();
    private transient Map<String, Command> commandMap = new HashMap<>();
    private transient Map<SubEntityKey, Entity> subEntityMap = new HashMap<>();
    private transient Entity fullEntity;
    /**
     * 如果存在subEntityKey，则说明当前实体是一个子集实体
     */
    private transient SubEntityKey subEntityKey;

    public Entity() {

    }

    /**
     * 对实体进行组装，对内部元素进行梳理和校验
     */
    public synchronized void build() {
        EntityBuilder entityBuilder = new EntityBuilder();
        entityBuilder.build();
    }

    /**
     * 拷贝当前实例
     *
     * @return
     */
    public Entity copy() {
        DataContractXmlSerializer serializer = new DataContractXmlSerializer();

        String xml = serializer.serializeToString(this);
        Entity copyEntity = (Entity) serializer.deserializeFromString(xml);

        this.onCopy(copyEntity);

        copyEntity.build();

        return copyEntity;
    }

    /**
     * 创建一个数据实体，它封装一个具体的业务对象，每次调用该方法返回的都是一个新的实例
     *
     * @return
     */
    public abstract IDataEntity createDataEntity();

    /**
     * 创建一个行列表数据实体，它负责当前实体的列表查询
     *
     * @return
     */
    public abstract IRowListDataEntity createRowListDataEntity();

    /**
     * 确保返回一个完整的实体（与subEntity对应）
     *
     * @return
     * @see #getSubEntity(List)
     */
    public Entity fullEntity() {
        return this.isSubEntity() ? this.fullEntity : this;
    }

    /**
     * 获取代码属性
     *
     * @return
     */
    public TextProperty getCodeProperty() {
        return codeProperty;
    }

    /**
     * 获取代码属性的名称
     *
     * @return
     */
    public String getCodePropertyName() {
        return codePropertyName;
    }

    /**
     * @param codePropertyName
     * @see #getCodePropertyName()
     */
    public void setCodePropertyName(String codePropertyName) {
        this.codePropertyName = codePropertyName;
    }

    /**
     * 从当前实体中，获取指定名称的命令，找不到返回null
     *
     * @param name
     * @return
     */
    public Command getCommand(String name) {
        return this.getCommand(name, false);
    }

    /**
     * 从当前实体中，获取指定名称的命令
     *
     * @param name
     * @param required 如果true，找不到会抛出异常
     * @return
     */
    public Command getCommand(String name, boolean required) {
        return EntityHelper.getCommand(fullEntity(), name, required);
    }

    /**
     * 获取实体的命令集合
     *
     * @return
     */
    public List<Command> getCommands() {
        return commands;
    }

    /**
     * @param commands
     * @see #getCommands()
     */
    public void setCommands(List<Command> commands) {
        this.commands = commands;
    }

    /**
     * 获取CreateByProperty，如果未声明则为null
     *
     * @return
     */
    public CreateByProperty getCreateByProperty() {
        return createByProperty;
    }

    /**
     * 获取CreateTimeProperty，如果未声明则为null
     *
     * @return
     */
    public CreateTimeProperty getCreateTimeProperty() {
        return createTimeProperty;
    }

    /**
     * 获取实体业务对象的元数据
     */
    @Override
    public final IDataObjectMeta getDataObjectMeta() {
        if (this.dataObjectMeta == null) {
            synchronized (this) {
                if (this.dataObjectMeta == null) {
                    this.dataObjectMeta = createDataObjectMeta();
                }
            }
        }

        return this.dataObjectMeta;
    }

    /**
     * 获取实体的标题，如果{@link #getEnableDefaultDisplayNameRule()}为true，则以${实体名称}为key从资源文件中获取，反之调用基类方法
     */
    @Override
    public String getDisplayName() {
        if (this.getEnableDefaultDisplayNameRule()) {
            String key = EntityHelper.getDisplayNameResouceKey(this);
            return StringResources.resolve(key);
        }

        return super.getDisplayName();
    }

    /**
     * 当前实体及其关联元素的{@link #getDisplayName()}方法，是否按默认的资源key从资源文件中获取
     *
     * @return
     */
    public Boolean getEnableDefaultDisplayNameRule() {
        return enableDefaultDisplayNameRule == null ? false : enableDefaultDisplayNameRule;
    }

    /**
     * @param enableDefaultDisplayNameRule
     * @see #getEnableDefaultDisplayNameRule()
     */
    public void setEnableDefaultDisplayNameRule(boolean enableDefaultDisplayNameRule) {
        this.enableDefaultDisplayNameRule = enableDefaultDisplayNameRule;
    }

    /**
     * 获取完整实体的名称
     */
    public String getFullEntityName() {
        return fullEntity().getName();
    }

    /**
     * 获取Id属性，如果未声明则为null
     *
     * @return
     */
    public IdProperty getIdProperty() {
        return idProperty;
    }

    /**
     * 获取名称属性
     *
     * @return
     */
    public TextProperty getNameProperty() {
        return nameProperty;
    }

    /**
     * 获取名称属性的名称
     *
     * @return
     */
    public String getNamePropertyName() {
        return namePropertyName;
    }

    /**
     * @param namePropertyName
     * @see #getNamePropertyName()
     */
    public void setNamePropertyName(String namePropertyName) {
        this.namePropertyName = namePropertyName;
    }

    /**
     * 根据属性别名获取属性，找不到返回null
     *
     * @param propertyAlias
     * @return
     * @see {@link Property#getAlias()}
     */
    public Property<?> getPropertyByAlias(String propertyAlias) {
        return EntityHelper.getPropertyByAlias(this, propertyAlias, false);
    }

    /**
     * 根据属性别名获取属性
     *
     * @param propertyAlias
     * @param required      如果true，找不到会抛出异常
     * @return
     * @see {@link Property#getAlias()}
     */
    public Property<?> getPropertyByAlias(String propertyAlias, boolean required) {
        return EntityHelper.getPropertyByAlias(this, propertyAlias, required);
    }

    /**
     * 获取实体对应的查询元数据，它是列表查询的必要元素
     *
     * @return
     */
    public abstract QueryMeta getQueryMeta();

    /**
     * 获取实体的状态属性，一个实体可以拥有0或1个状态属性
     *
     * @return
     */
    public StatusProperty[] getStatusProperties() {
        return statusProperties;
    }

    /**
     * 获取当前实体的子集实体，只包含指定的属性
     *
     * <p>
     * 子集实体的创建用到{@link #copy()}方法，有一定代价，因此每个子实体都会被缓存
     *
     * @param propertyAliasList
     * @return
     */
    public Entity getSubEntity(List<String> propertyAliasList) {
        Assert.isTrue(!this.isSubEntity(), "this is a subEntity,can not invoke getSubEntity(List<String>) method");
        Assert.notEmpty(propertyAliasList, "paremeter 'propertyNameList' is required!");

        // TODO 校验propertyAliasList的合法性，去掉错误的属性

        SubEntityKey key = new SubEntityKey(propertyAliasList);
        Entity subEntity = subEntityMap.get(key);

        if (subEntity == null) {
            synchronized (this) {
                subEntity = subEntityMap.get(key);

                if (subEntity == null) {
                    subEntity = createSubEntity(key);
                    subEntityMap.put(key, subEntity);
                }
            }
        }

        return subEntity;
    }

    /**
     * 获取UpdateByProperty，如果未声明则为null
     *
     * @return
     */
    public UpdateByProperty getUpdateByProperty() {
        return updateByProperty;
    }

    /**
     * 获取UpdateTimeProperty，如果未声明则为null
     *
     * @return
     */
    public UpdateTimeProperty getUpdateTimeProperty() {
        return updateTimeProperty;
    }

    /**
     * 获取指定的验证器配置，找不到返回null
     *
     * @param name
     * @return
     */
    public Validation getValidation(String name) {
        return this.getValidation(name, false);
    }

    /**
     * 获取指定的验证器配置
     *
     * @param name
     * @param required 如果true，找不到会抛出异常
     * @return
     */
    public Validation getValidation(String name, boolean required) {
        return EntityHelper.getValidation(this.fullEntity(), name, required);
    }

    /**
     * 获取验证器配置集合
     *
     * @return
     */
    public List<Validation> getValidations() {
        return validations;
    }

    /**
     * @param validations
     * @see Entity#getValidations()
     */
    public void setValidations(List<Validation> validations) {
        this.validations = validations;
    }

    /**
     * 当前实体是否是一个字实体
     *
     * @return
     */
    public boolean isSubEntity() {
        return subEntityKey != null;
    }

    public synchronized void sort() {
        EntityBuilder entityBuilder = new EntityBuilder();
        entityBuilder.sort();
    }

    /**
     * 往当前实体添加一个命令
     *
     * @param command 如果参数实现了{@link ISupportEntityLimit},则会进行实体类型校验
     */
    protected void addCommand(Command command) {
        Assert.notNull(command, "parameter 'command' is required");

        this.checkEntityLimit(command);
        this.commands.add(command);
    }

    /**
     * 往当前实体添加一个验证器
     *
     * @param validation 如果参数实现了{@link ISupportEntityLimit},则会进行实体类型校验
     */
    protected void addValidation(Validation validation) {
        Assert.notNull(validation, "parameter 'validation' is required");

        this.checkEntityLimit(validation);
        this.validations.add(validation);
    }

    /**
     * 创建实体的业务对象元数据
     *
     * @return
     */
    protected abstract IDataObjectMeta createDataObjectMeta();

    /**
     * 创建指定属性的子实体，对于同一个key，只会调用一次
     *
     * @param key
     * @return
     */
    protected Entity createSubEntity(SubEntityKey key) {
        Entity subEntity = this.copy();

        subEntity.setSubEntityKey(key);
        subEntity.setName(this.getName() + "@" + key.propertyNames);

        List<String> usedPropertyNameList = new ArrayList<>(Arrays.asList(key.getPropertyNameArray()));

        this.removeUnUsedProperties(subEntity, usedPropertyNameList);
        this.removeUnUsedEntities(subEntity);

        subEntity.getCommands().clear();
        subEntity.getValidations().clear();

        subEntity.fullEntity = this;
        subEntity.build();

        return subEntity;
    }

    @Override
    protected Entity findRoot() {
        return this;
    }

    /**
     * 如果当前是一个子实体，返回对应的key
     *
     * @return
     */
    protected SubEntityKey getSubEntityKey() {
        return subEntityKey;
    }

    protected void setSubEntityKey(SubEntityKey subEntityKey) {
        this.subEntityKey = subEntityKey;
    }

    Map<String, Command> getCommandMap() {
        return commandMap;
    }

    Map<String, AbstractEntity> getEntityMap() {
        return entityMap;
    }

    Map<String, Property<?>> getPropertyMap() {
        return propertyMap;
    }

    Map<String, Validation> getValidationMap() {
        return validationMap;
    }

    private void removeUnUsedEntities(AbstractEntity entity) {
        List<AbstractEntity> unUsedEntities = new ArrayList<AbstractEntity>();

        for (AbstractEntity childEntity : entity.getChildEntities()) {
            boolean hasProperty = childEntity.getProperties().size() > 0;
            if (!hasProperty) {
                unUsedEntities.add(childEntity);
            }
        }

        if (!CollectionUtils.isEmpty(unUsedEntities)) {
            entity.getChildEntities().removeAll(unUsedEntities);
        }

        for (AbstractEntity childEntity : entity.getChildEntities()) {
            this.removeUnUsedEntities(childEntity);
        }

    }

    private void removeUnUsedProperties(AbstractEntity entity, List<String> usedPropertyNameList) {
        List<Property<?>> unUsedProperties = new ArrayList<>();

        for (Property<?> property : entity.getProperties()) {
            if (!usedPropertyNameList.contains(property.getAlias())) {
                unUsedProperties.add(property);
            } else {
                usedPropertyNameList.remove(property.getAlias());
            }
        }

        if (!CollectionUtils.isEmpty(unUsedProperties)) {
            entity.getProperties().removeAll(unUsedProperties);
        }

        for (AbstractEntity childEntity : entity.getChildEntities()) {
            this.removeUnUsedProperties(childEntity, usedPropertyNameList);
        }
    }

    class EntityBuilder {

        private NameGenerator nameGenerator;

        EntityBuilder() {
            this.nameGenerator = new NameGenerator();
        }

        void build() {
            this.clear();

            this.buildEntityProperties(Entity.this);
            this.setSpecialPropertyIfnecessary();
            this.buildValidations();
            this.buildCommands();

            this.sort();
        }

        void setSpecialPropertyIfnecessary() {
            // 代码属性
            String codePropertyName = Entity.this.getCodePropertyName();
            if (!StringUtils.isEmpty(codePropertyName)) {
                Entity.this.codeProperty = (TextProperty) Entity.this.getProperty(codePropertyName, false);
            }

            // 名称属性
            String namePropertyName = Entity.this.getNamePropertyName();
            if (!StringUtils.isEmpty(namePropertyName)) {
                Entity.this.nameProperty = (TextProperty) Entity.this.getProperty(namePropertyName, false);
            }

            // id属性
            Object[] idProperties = Entity.this.getProperties().stream().filter(p -> p instanceof IdProperty).toArray();
            if (idProperties.length > 0) {
                if (idProperties.length > 1) {
                    throw ExceptionFactory.createRuntimeException(
                            "pisces-domain.EntityBuilder.setSpecialPropertyIfnecessary-001",
                            "Entity:{0} has more than one Id Property!", Entity.this.toString());
                }

                Entity.this.idProperty = (IdProperty) idProperties[0];
            }

            // 状态属性
            Entity.this.statusProperties = Entity.this.getProperties().stream().filter(p -> p instanceof StatusProperty)
                    .toArray(l -> new StatusProperty[l]);

            //创建人属性
            Object[] createByProperty = Entity.this.getProperties().stream()
                    .filter(p -> p instanceof CreateByProperty).toArray();
            if (createByProperty.length > 0) {
                if (createByProperty.length > 1) {
                    throw ExceptionFactory.createRuntimeException(
                            "pisces-domain.EntityBuilder.setSpecialPropertyIfNecessary-002",
                            "Entity:{0} has more than one CreateByProperty Property!", Entity.this.toString());
                }

                Entity.this.createByProperty = (CreateByProperty) createByProperty[0];
            }

            // 创建时间属性
            Object[] createTimeProperty = Entity.this.getProperties().stream()
                    .filter(p -> p instanceof CreateTimeProperty).toArray();
            if (createTimeProperty.length > 0) {
                if (createTimeProperty.length > 1) {
                    throw ExceptionFactory.createRuntimeException(
                            "pisces-domain.EntityBuilder.setSpecialPropertyIfNecessary-002",
                            "Entity:{0} has more than one CreateTimeProperty Property!", Entity.this.toString());
                }

                Entity.this.createTimeProperty = (CreateTimeProperty) createTimeProperty[0];
            }

            //更新人属性
            Object[] updateByProperty = Entity.this.getProperties().stream()
                    .filter(p -> p instanceof UpdateByProperty).toArray();
            if (updateByProperty.length > 0) {
                if (updateByProperty.length > 1) {
                    throw ExceptionFactory.createRuntimeException(
                            "pisces-domain.EntityBuilder.setSpecialPropertyIfNecessary-003",
                            "Entity:{0} has more than one UpdateByProperty Property!", Entity.this.toString());
                }

                Entity.this.updateByProperty = (UpdateByProperty) updateByProperty[0];
            }

            //更新时间属性
            Object[] updateTimeProperty = Entity.this.getProperties().stream()
                    .filter(p -> p instanceof UpdateTimeProperty).toArray();
            if (updateTimeProperty.length > 0) {
                if (updateTimeProperty.length > 1) {
                    throw ExceptionFactory.createRuntimeException(
                            "pisces-domain.EntityBuilder.setSpecialPropertyIfNecessary-004",
                            "Entity:{0} has more than one UpdateTimeProperty Property!", Entity.this.toString());
                }

                Entity.this.updateTimeProperty = (UpdateTimeProperty) updateTimeProperty[0];
            }
        }

        void sort() {
            this.sortProperties(Entity.this);
        }

        private void buildCommands() {
            for (Command command : Entity.this.commands) {
                String name = command.getName();

                if (StringUtils.isEmpty(name)) {
                    name = this.nameGenerator.generator(command.getClass());
                }

                Command existCommand = Entity.this.commandMap.get(name);
                if (existCommand != null) {
                    throw ExceptionFactory.createRuntimeException("pisces-domain.EntityBuilder.buildCommands-001",
                            "Command:{0} is exist!", name);
                }

                command.setHostEntity(Entity.this);
                command.setEnableDefaultDispalyNameRule(Entity.this.getEnableDefaultDisplayNameRule());

                if (command instanceof ISupportEntityBuild) {
                    ((ISupportEntityBuild) command).build(Entity.this);
                }

                Entity.this.commandMap.put(name, command);
            }
        }

        private void buildEntityProperties(AbstractEntity entity) {
            String name = entity.getName();

            AbstractEntity existEntity = Entity.this.entityMap.get(name);
            if (existEntity != null) {
                throw ExceptionFactory.createRuntimeException("pisces-domain.EntityBuilder.buildEntityProperties-001",
                        "entity:{0} is exist!", name);
            }

            Entity.this.entityMap.put(name, entity);

            for (Property<?> property : entity.getProperties()) {
                String alias = property.getAlias();

                Property<?> existProperty = Entity.this.propertyMap.get(alias);
                if (existProperty != null) {
                    throw ExceptionFactory.createRuntimeException(
                            "pisces-domain.EntityBuilder.buildEntityProperties-002",
                            "property alias:{0} is exist in entity!", alias);

                }

                property.setHostEntity(entity);

                Entity rootEntity = EntityHelper.getRootEntity(entity);
                property.setEnableDefaultDispalyNameRule(rootEntity.getEnableDefaultDisplayNameRule());

                if (property instanceof ISupportEntityBuild) {
                    ((ISupportEntityBuild) property).build(Entity.this);
                }

                Entity.this.propertyMap.put(alias, property);
            }

            for (ChildEntity childEntity : entity.getChildEntities()) {
                childEntity.setParentEntity(entity);
                this.buildEntityProperties(childEntity);
            }
        }

        private void buildValidations() {
            for (Validation validation : Entity.this.validations) {
                String name = validation.getName();

                if (StringUtils.isEmpty(name)) {
                    name = this.nameGenerator.generator(validation.getClass());
                }

                Validation existValidation = Entity.this.validationMap.get(name);
                if (existValidation != null) {
                    throw ExceptionFactory.createRuntimeException("pisces-domain.EntityBuilder.buildValidations-001",
                            "Validation:{0} is exist!", name);

                }

                if (validation instanceof ISupportEntityBuild) {
                    ((ISupportEntityBuild) validation).build(Entity.this);
                }

                Entity.this.validationMap.put(name, validation);
            }
        }

        private void clear() {
            Entity.this.entityMap.clear();
            Entity.this.propertyMap.clear();
            Entity.this.validationMap.clear();
            Entity.this.commandMap.clear();
            Entity.this.subEntityMap.clear();

            Entity.this.dataObjectMeta = null;
            Entity.this.codeProperty = null;
            Entity.this.nameProperty = null;
        }

        private void sortProperties(AbstractEntity entity) {
            PropertyCollection properties = entity.getProperties();
            properties.sort(ISupportSort.SORT_COMPARATOR);

            for (ChildEntity childEntity : entity.getChildEntities()) {
                this.sortProperties(childEntity);
            }
        }

        private class NameGenerator {
            private final Map<String, AtomicInteger> map = new HashMap<>();

            public String generator(Class<? extends Element> elementType) {
                String key = elementType.getSimpleName();

                AtomicInteger serialNo = map.get(key);
                if (serialNo == null) {
                    serialNo = new AtomicInteger(0);
                    map.put(key, serialNo);
                }

                int val = serialNo.getAndIncrement();
                return val == 0 ? key : (key + val);
            }
        }
    }

}
